/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.inflator;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGInterfaceList;
import org.jfab.core.collection.CGMethodDeclarationList;
import org.jfab.core.collection.CGPropertyList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.factory.CGFieldFactory;
import org.jfab.core.factory.CGInterfaceFactory;
import org.jfab.core.factory.CGMethodDeclarationFactory;
import org.jfab.core.factory.CGTypeFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGField;
import org.jfab.core.model.CGInterface;
import org.jfab.core.model.CGMethodDeclaration;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.util.TypeParameterMap;

/**
 * Provides a default implementation of a code generator interface inflator.
 */
public final class DefaultCGInterfaceInflator extends
        CGCompilationUnitInflator<CGInterface> implements CGInterfaceInflator
{
    /** Interface factory. */
    private final CGInterfaceFactory _interfaceFactory;

    /** Method declaration comparator. */
    private final Comparator<CGMethodDeclaration> _methodDeclarationComparator;

    /** Method declaration factory. */
    private final CGMethodDeclarationFactory _methodDeclarationFactory;

    /**
     * Construct this object with the given parameters.
     * 
     * @param stringFactory String factory. (required)
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory. (required)
     * @param fieldFactory Field factory. (required)
     * @param fieldComparator Field comparator. (required)
     * 
     * @param methodDeclarationFactory Method declaration factory. (required)
     * @param methodDeclarationComparator Method declaration comparator.
     *            (required)
     * @param interfaceFactory Interface factory. (required)
     */
    public DefaultCGInterfaceInflator(final StringFactory stringFactory,
            final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory,
            final CGFieldFactory fieldFactory,
            final Comparator<CGField> fieldComparator,
            final CGMethodDeclarationFactory methodDeclarationFactory,
            final Comparator<CGMethodDeclaration> methodDeclarationComparator,
            final CGInterfaceFactory interfaceFactory)
    {
        super(stringFactory, cgTypeUtils, cgTypeFactory, fieldFactory,
                fieldComparator);

        Validate.notNull(methodDeclarationFactory,
                "methodDeclarationFactory is null");
        Validate.notNull(methodDeclarationComparator,
                "methodDeclarationComparator is null");
        Validate.notNull(interfaceFactory, "interfaceFactory is null");

        _methodDeclarationFactory = methodDeclarationFactory;
        _methodDeclarationComparator = methodDeclarationComparator;
        _interfaceFactory = interfaceFactory;
    }

    @Override
    public final CGInterface inflate(final CGInterface cgInterface,
            final CGInterfaceList superInterfaces)
    {
        final CGTypeList referencedTypes = new CGTypeList(
                cgInterface.getReferencedTypes());
        final String packageName = cgInterface.getPackageName();
        final CommentList commentLines = new CommentList(
                cgInterface.getCommentLines());
        final CGAnnotationList annotations = new CGAnnotationList(
                cgInterface.getAnnotations());
        final ModifierList modifiers = new ModifierList(
                cgInterface.getModifiers());
        final String interfaceName = cgInterface.getName();
        final List<String> typeParameters = cgInterface.getTypeParameters();
        final CGTypeList extendsTypes = new CGTypeList(
                cgInterface.getExtendsTypes());
        final TypeParameterMap typeParameterMap = createTypeParameterMap(
                cgInterface, superInterfaces);
        final CGFieldList fields = createFields(cgInterface, superInterfaces,
                typeParameterMap);
        final CGMethodDeclarationList methods = createMethodDeclarations(cgInterface);

        return getInterfaceFactory().createInterface(referencedTypes,
                packageName, commentLines, annotations, modifiers,
                interfaceName, typeParameters, extendsTypes, fields, methods);
    }

    @Override
    protected CGFieldList createFields(final CGInterface cgInterface,
            final CGInterfaceList superInterfaces,
            final TypeParameterMap typeParameterMap)
    {
        final CGFieldList answer = new CGFieldList();

        answer.addAll(cgInterface.getFields());

        return answer;
    }

    /**
     * @param cgInterface Code generator interface.
     * 
     * @return new methods created from the given parameter.
     */
    protected CGMethodDeclarationList createMethodDeclarations(
            final CGInterface cgInterface)
    {
        final CGMethodDeclarationFactory methodFactory = getMethodDeclarationFactory();
        final CGPropertyList properties = new CGPropertyList(
                cgInterface.getProperties());

        final CGAnnotationList annotations = null;
        final ModifierList accessorModifiers = null;
        final CGMethodDeclarationList answer = new CGMethodDeclarationList();
        answer.addAll(methodFactory.createPropertyAccessorMethods(annotations,
                accessorModifiers, properties));

        final boolean isSuperPropertyChangeSupported = isSuperPropertyChangeSupported(cgInterface);
        final ModifierList mutatorModifiers = null;

        if (cgInterface.isPropertyChangeSupported())
        {
            answer.addAll(methodFactory
                    .createPropertyMutatorPropertyChangeMethods(annotations,
                            mutatorModifiers, properties));
            if (!isSuperPropertyChangeSupported)
            {
                answer.add(methodFactory.createAddPropertyChangeListenerMethod(
                        annotations, mutatorModifiers));
                answer.add(methodFactory
                        .createAddPropertyChangeListenerNamedMethod(
                                annotations, mutatorModifiers));
                answer.add(methodFactory
                        .createRemovePropertyChangeListenerMethod(annotations,
                                mutatorModifiers));
                answer.add(methodFactory
                        .createRemovePropertyChangeListenerNamedMethod(
                                annotations, mutatorModifiers));
            }
        }
        else
        {
            answer.addAll(methodFactory.createPropertyMutatorMethods(
                    mutatorModifiers, properties));
        }

        if (CollectionUtils.isNotEmpty(cgInterface.getMethods()))
        {
            answer.addAll(cgInterface.getMethods());
        }

        Collections.sort(answer, getMethodDeclarationComparator());

        return answer;
    }

    @Override
    protected TypeParameterMap createTypeParameterMap(
            final CGInterface cgInterface, final CGInterfaceList superInterfaces)
    {
        final TypeParameterMap typeParameterMap = new TypeParameterMap(
                getCgTypeUtils());
        for (int i = 0; i < cgInterface.getExtendsTypes().size(); i++)
        {
            final CGType extendsType = cgInterface.getExtendsTypes().get(i);
            final String extendsInterface = getCgTypeUtils()
                    .toSimpleComponentNameString(extendsType);
            final String typeParameter = StringUtils.substringBetween(
                    getCgTypeUtils().toNameString(extendsType), "<", ">");

            if (StringUtils.isNotEmpty(typeParameter))
            {
                final CGType cgType = getCgTypeFactory().createType(
                        typeParameter);
                final CGInterface superInterface = superInterfaces
                        .findByName(extendsInterface);

                if (superInterface != null)
                {
                    typeParameterMap.put(superInterface.getTypeParameters()
                            .get(0), cgType);
                }
            }
        }

        return typeParameterMap;
    }

    /**
     * @return the methodDeclarationComparator
     */
    protected Comparator<CGMethodDeclaration> getMethodDeclarationComparator()
    {
        return _methodDeclarationComparator;
    }

    /**
     * @return the methodDeclarationFactory
     */
    protected CGMethodDeclarationFactory getMethodDeclarationFactory()
    {
        return _methodDeclarationFactory;
    }

    /**
     * @return the interfaceFactory
     */
    private CGInterfaceFactory getInterfaceFactory()
    {
        return _interfaceFactory;
    }

    /**
     * @param cgInterface Code generator interface.
     * @param methodName Method name.
     * 
     * @return true if the given parameter's superclasses specify a method of
     *         the given name.
     */
    private boolean isSuperClassMethod(final CGInterface cgInterface,
            final String methodName)
    {
        boolean answer = false;

        final List<CGType> extendsTypes = cgInterface.getExtendsTypes();

        if (!CollectionUtils.isEmpty(extendsTypes))
        {
            final int size = extendsTypes.size();

            for (int i = 0; !answer && (i < size); i++)
            {
                final CGType extendsType = extendsTypes.get(i);
                answer = isSuperClassMethod(extendsType, methodName);
            }
        }

        return answer;
    }

    /**
     * @param cgInterface Code generator interface.
     * 
     * @return true if the given parameter's superclasses support property
     *         changes.
     */
    private boolean isSuperPropertyChangeSupported(final CGInterface cgInterface)
    {
        final String methodName = "addPropertyChangeListener";

        return isSuperClassMethod(cgInterface, methodName);
    }
}
